package com.ksvip.next.components.sequence.single;

import java.sql.SQLException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.ksvip.next.components.sequence.handler.Sequence;
import com.ksvip.next.components.sequence.single.core.SingleSequenceRange;
import com.ksvip.next.components.sequence.single.dao.SingleSequenceDao;
import com.ksvip.next.components.sequence.single.exception.SingleSequenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class SingleSequence implements Sequence {
    private static final Logger          log  = LoggerFactory.getLogger(SingleSequence.class);
    private String                       name;
    private SingleSequenceDao sequenceDao;
    private volatile SingleSequenceRange currentRange;
    private final Lock                   lock = new ReentrantLock();

    public SingleSequence() {
    }

    public void init() throws SingleSequenceException, SQLException {
        this.sequenceDao.adjust(this.name);
    }

    @Override
    public long nextValue() {
        if (this.currentRange == null) {
            this.lock.lock();
            try {
                if (this.currentRange == null) {
                    this.currentRange = this.sequenceDao.nextRange(this.name);
                }
            } finally {
                this.lock.unlock();
            }
        }

        long value;
        do {
            if (this.currentRange.isOver()) {
                this.lock.lock();

                try {
                    if (this.currentRange.isOver()) {
                        this.currentRange = this.sequenceDao.nextRange(this.name);
                    }
                } finally {
                    this.lock.unlock();
                }
            }
            value = this.currentRange.getAndIncrement();
        } while (value == -1L);

        if (value < 0L) {
            throw new SingleSequenceException("Sequence value overflow, value = " + value);
        } else {
            return value;
        }
    }

    public String getName() {
        return this.name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public SingleSequenceDao getSequenceDao() {
        return this.sequenceDao;
    }

    public void setSequenceDao(final SingleSequenceDao sequenceDao) {
        this.sequenceDao = sequenceDao;
    }
}
